feat: support compose secrets via podman secret store#30
Merged
Conversation
validate_secrets picks the source by string-ness (isinstance(def.get(k), str)),
but secret_create_lines/secret_referenced_variables branched on "file" in def
(key presence). A def like {file: [x], environment: VAR} validated as an env
secret yet crashed raw in the file branch (TypeError in Path), violating the
refuse-loudly contract. Align emission with validation's string-ness check.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add compose
secretssupport — the first Bucket B feature (real translation, not a flag pass-through). Top-levelfile:/environment:secret definitions become pod-namespacedpodman secret createcalls; service references becomepodman run --secretmounts at/run/secrets/<target>; the store is torn down in the existing EXIT trap.external: trueis rejected (honest-subset boundary).A new deep module
compose2pod/secrets.pyowns the translation:parsing.pygates it,emit.pyweaves the store lifecycle in. Import direction is one-way (emit/parsing → secrets → keys/shell/exceptions).What's supported
secrets:with exactly onefile:(host path, resolved against--project-dir,${VAR}expands at run time) orenvironment:(piped viaprintf '%s' "${VAR-}").secrets:short form[name]and full long form{source, target, uid, gid, mode}(modeint→octal string, string passthrough).<pod>-<name>) isolate concurrent CI runs; trap removes them alongside the pod.referenced_variables()stderr note.Security hardening
The whole-branch review found command injection: secret names and env-var names flowed into generated shell raw (they can't be
shlex.quoted like values). Closed by charset-validating both at the gate:^[a-zA-Z0-9][a-zA-Z0-9_.-]*$, env-var names^[a-zA-Z_][a-zA-Z0-9_]*$— every admitted char is shell-inert in an unquoted single token.re.fullmatch(an initialre.matchadmitted a trailing\n, since$matches before a newline — that reopened injection by splitting the shell line).uid/gid/modereject non-scalar/bool; the whole--secretarg isshlex.quoted.file-key presence, so a malformed mixed def is refused loudly rather than crashing raw.An independent opus re-review confirmed no remaining injection path (charset shell-inert, ASCII classes reject unicode,
MULTILINEunset, every emit path gated byvalidate).Testing
just test-ciat 100% coverage (247 tests),just lint-ciclean,just check-planningOK. Validation accept/reject matrix, emission (file/env create lines,--secretopts, mode int/string, pod-namespacing, trapsecret rm, referenced vars), injection regressions (metachar, newline, unicode-adjacent), and anemit_scriptintegration.Docs / planning
planning/changes/2026-07-10.02-secrets.mdarchitecture/supported-subset.md